#pragma once

#define _CRT_SECURE_NO_DEPRECATE	1

//#include "../vc9/testdll/TestExport.h"

#include <windows.h>

#include <vector>
using namespace std;

#include "crtmath.h"
#include "ISpace.h"

const double M_PI= 3.1415926535897932384626433832795;
const double M_PI_2 = 1.5707963267948966192313216916398;

// Mini version of vertex
typedef VEC3 TCRTVertexMini;

// Full version of vertex
struct TCRTVertexFull
{
	VEC3 position;
	VEC3 normal;
	VEC2 texcoord;

	TCRTVertexFull() {};
	TCRTVertexFull(const TCRTVertexFull& s)
	{
		position = s.position;
		normal = s.normal;
		texcoord = s.texcoord;
	}
};

// Topology
struct TCRTTriangle
{
	int index[3];
};

// Helper data to acceleration triangle-ray IT
//  each TCRTTriangle maps to a TCRTTriangleHelper
struct TCRTTriangleHelper
{
	FPlane	plane;	// plane equation

	/*
	When we have a triangle v0v1v2, and a point P on the same plane, 
	we express P in the barycentric axis as following:

	P = v0 + U * (v1 - v0) + V * (v2 - v0);


	If we solve U/V from above equation, we'll get:

	U = (P % perp2 - v0 % perp2) / (V01 % perp2);
	V = (P % perp1 - v0 % perp1) / (V02 % perp1);

	in which % denotes "dot product", 
	V01 defines the vector v0->v1
	V02 defines the vector v0->v2
	perp1 defines the unit vector perpendicular to vector v0->v1 (v2 at the positive side)
	perp2 defines the unit vector perpendicular to vector v0->v2 (v1 at the positive side)

	So if we make it more clear : 

	U = (P % perp2) * a1 + b1;
	V = (P % perp1) * a2 + b2;

	a1 = 1 / (v1 % perp2); 
	b1 = - v0 % perp2 / (v1 % perp2);
	a2 = 1 / (v2 % perp1);
	b2 = - v0 % perp1 / (v2 % perp1);
	*/

	//float	a1, a2;
	float	b1, b2;

	VEC3	perp1;	// perpendicular to v0v1, note that this is already multiplied with a2
	VEC3	perp2;	// perpendicular to v0v2, note that this is already multiplied with a1


	// For ray - scene IT, to avoid multiple testings between ray and the same triangle, 
	// we maintain a list of "tested" helpers, 
	// when one helper has been tested against the ray, it'll be put into the list and 
	// no further testing with the same ray will happen

	// The list will be released as soon as the ray - scene IT is over
	TCRTTriangleHelper* nextTestedHelper;	
};


// Material of mesh
struct TCRTMaterial
{
	COLOR	ambient;
	COLOR	diffuse;
	COLOR	specular;
	float	shininess;	// the exponent component
};

// Vertex hash, used to build up to vertex buffer when loading ASE/OBJ file
struct TVertexHashEntry
{
	int					vindex;	// vertex index
	TVertexHashEntry*	next;
};

// The mesh
struct TCRTMesh
{
	int				meshID;
	TCRTMaterial	material;
	WCHAR			name[128];

	std::vector<TCRTVertexFull>	vertices_f;	
	std::vector<TCRTTriangle>	triangles;
	std::vector<TCRTTriangleHelper> triangleHelpers;

	std::vector<TCRTVertexMini>	vertices_m;
	AABB aabb;

	std::vector<TVertexHashEntry*> hash;

	// Merge duplicated vertices in vertices_f
	// Generate vertices_m
	// Compute aabb
	bool Optimize();	

	TCRTMesh();
	~TCRTMesh();

	int	 AddVertex( int vpos, TCRTVertexFull& v );
	void ClearCache();
};

enum ECRTLightType
{
	CRT_LT_OMNI = 0,
	CRT_LT_SPOT,
	CRT_LT_DIRECTIONAL,
	CRT_LT_SKY,

	CRT_LT_NUM,
};

struct TCRTLight
{
	ECRTLightType type;

	VEC3	color;		// Color & intensity
	VEC3	position;	// For omni and spot only
	VEC3	direction;	// For spot and directional only
	
	// Get a direction pointing to the light source
	VEC3	GetDirection(VEC3& ObjectPosition,VEC3& LightPosition = VEC3(0,0,0))
	{
		VEC3 result(0,0,0);
		
		if (CRT_LT_DIRECTIONAL == type)
		{
			result = -direction;
		}
		else if (CRT_LT_SKY == type)
		{
			// TONGJI_TODO
			
			result =LightPosition;
		}
		else
		{
			result = position - ObjectPosition;
			result.Normalize();
		}

		return result;
	}
};

// Interesection description for RAY/Triangle test
struct TSceneIntersection
{
	int		meshIndex;
	int		index[3];		// p0, p1, p2
	float	u, v;			// the intersection point is : p0 + (p1 - p0) * u + (p2 - p0) * v
	float	dist;

	TSceneIntersection();
};

// scene representation
class TCRTScene
{
public: 
	std::vector<TCRTMesh*>	meshes;
	std::vector<TCRTLight>	lights;

	AABB aabb;	

	TCRTScene();
	~TCRTScene();
	// Optimize each mesh
	// Scene management (octree...)
	bool Optimize();

	bool LoadSceneFromASE(TCHAR* filename);
	bool LoadSceneFromOBJ(TCHAR* filename);

	bool Check();

	int GetMeshCount() { return static_cast<int>(meshes.size()); }
	int GetLightCount() { return static_cast<int>(lights.size()); }
	int GetVertexCount();
	int GetTriangleCount();

	void ResetStat();
	size_t  GetRayTriangleTestNum();
	size_t  GetRayAABBTestNum();
	double GetRayTriangleTestTime();

	bool TraceRay(RAY& ray, vector<TSceneIntersection>& outHits, TraceType traceType);

protected:
	ISpace* mpSpace;

	size_t mnRayTriangleTest;
	size_t mnRayAABBTest;
};


// camera model
struct TCRTCamera
{
	MAT4 viewM;
	MAT4 projM;
	VEC3 position;

	bool Check();
};


// output image format (in-memory)
enum ECRTOutputFormat
{
	CRT_OF_RGB = 0,
	CRT_OF_BGR,
	CRT_OF_HDR_RGB,
	CRT_OF_HDR_BGR,
};

// call back functions for the ray tracer
typedef void (CALLBACK *UserStartFunc)		();		// callback function for job start point
typedef void (CALLBACK *UserStopFunc)		();		// callback function for job end point
typedef void (CALLBACK *UserProgressFunc)	(float progress, void *userdata);	// callback function for job progress. progress - [0, 1]

#define CRT_MAX_OUTPUT_WIDTH	(1600)
#define CRT_MAX_OUTPUT_HEIGHT	(1200)

// output standard
struct TCRTOutput
{
	// [INPUT]
	int		imagew;		
	int		imageh;
	ECRTOutputFormat	format;	
	UserStartFunc		startfunc;		// callback functions
	UserStopFunc		stopfunc;
	UserProgressFunc	progressfunc;
	bool	shadow;			// shadow enabled ?

	// [OUTPUT]
	__int64	stime_s;	// start time, in s
	int		stime_ms;	// start time, in ms
	float	timeused;	// in s
	int		nray;		// total ray issued
	int		nraytriangle;	// total ray-triangle intersection testing
	int		nrayaabb;		// total ray-aabb intersection testing
	double  raytritime;		// total ray-triangle intersection testing

	int		nDirtyRectX;
	int		nDirtyRectY;
	int		nDirtyRectWidth;
	int		nDirtyRectHeight;

	// [INPUT, OUTPUT]
	void*	buffer;		// output buffer, size = imagew * imageh * 3 * 1or4

	bool SaveToTGA(TCHAR* filename);
	bool Check();
};

// china ray trace
class CRT
{
public :
	CRT();
	~CRT();

	void SetCamera(TCRTCamera& _camera) 
	{ 
		if (memcmp(&camera, &_camera, sizeof(TCRTCamera)))
		{
			camera = _camera;
			dirty = true;
		}
	}
	const TCRTCamera& GetCamera() { return camera; }

	// it's better to call folllowing function each time before Run()
	void SetOutput(TCRTOutput& _output) 
	{ 
		if (memcmp(&output, &_output, sizeof(TCRTOutput)))
		{
			output = _output;
			dirty = true;
		}
	}
	const TCRTOutput& GetOutput() { return output; }

	// the scene
	void SetScene(TCRTScene* _scene)
	{
		if (scene != _scene)
		{
			scene = _scene;
			dirty = true;
		}
	}
	const TCRTScene* GetScene() { return scene; }

	int Run();

protected :
	TCRTOutput		output;
	TCRTCamera		camera;
	TCRTScene*		scene;
	//vector<VEC3> rays;
	bool			dirty;

	void Trace();
	VEC3 Shade(RAY& ray, VEC3& pos, VEC3& normal, TCRTMaterial& material, int depth);

	// return value: hit ?
	bool TraceRay(RAY& ray, int depth, VEC3& shading);

	void TraceCluster(int clusterX, int clusterY, int clusterWidth, int clusterHeight);
	void ProgressUpdate(
		int nDirtyRectX, 
		int nDirtyRectY, 
		int nDirtyRectWidth,
		int nDirtyRectHeight,
		int nPrimaryRay, 
		float fFinishedPercent);
	void UpdateViewProjectionMatrix();


private: 

	static const int mClusterWidth = 128;
	static const int mClusterHeight = 128;

	D3DXMATRIX mProjectionMatrix;
	D3DXMATRIX mViewMatrix;
	D3DVIEWPORT9 mViewport;
};